/**
 * Copyright (c) 2015-2017, Henry Yang 杨勇 (gismail@foxmail.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.lambkit.dao.record;

import com.jfinal.plugin.activerecord.IAtom;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.Page;
import com.jfinal.plugin.activerecord.SqlPara;
import com.lambkit.db.sql.Columns;
import com.lambkit.db.sql.Example;

import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.function.Function;

/**
 * LambkitApiService接口
 */
public interface LambkitApiService<M extends LambkitRecord> {
	
	String getTableName();

	M findById(Object idValue);
	M findByPrimaryKey(Object id);

	M findFirst(SqlPara sqlPara);
	M findFirst(Example example);
	M findFirst(Columns columns);
	M findFirst(Columns columns, String orderby);

	List<M> find(SqlPara sqlPara);

	List<M> find(Example example, Integer count);
	List<M> find(Columns columns, Integer count);
	List<M> find(Columns columns, String orderby, Integer count);

	List<M> find(Example example);
	List<M> find(Columns columns);
	List<M> find(Columns columns, String orderby);

	List<M> findAll();
	/**
	 * 分页查询数据
	 * @return
	 */
	Page<M> paginate(int pageNumber, int pageSize, SqlPara sqlPara);
	Page<M> paginate(Integer pageNumber, Integer pageSize, Example example);
	Page<M> paginate(Integer pageNumber, Integer pageSize, Columns columns);
	Page<M> paginate(Integer pageNumber, Integer pageSize, Columns columns, String orderby);
	/**
	 * offet page
	 * @return
	 */
	Page<M> paginate(Example example, Integer offset, Integer limit);
	Page<M> paginate(Columns columns, Integer offset, Integer limit);
	Page<M> paginate(Columns columns, String orderby, Integer offset, Integer limit);
	/**
	 * 获取数量
	 * @return
	 */
	Long count(Example example);
	Long count(Columns columns);

	boolean save(M record);

	boolean delete(M record);
	boolean deleteById(Object id);
	boolean deleteByIds(Object ...ids);
	int delete(SqlPara sqlPara);
	int delete(Example example);
	int delete(Columns columns);

	boolean update(M record);
	int update(SqlPara sqlPara);
	int update(M record, Example example);
	int update(M record, Columns columns);

	/**
	 * Execute update, insert or delete sql statement.
	 * @param sql an SQL statement that may contain one or more '?' IN parameter placeholders
	 * @param paras the parameters of sql
	 * @return either the row count for <code>INSERT</code>, <code>UPDATE</code>,
	 *         or <code>DELETE</code> statements, or 0 for SQL statements
	 *         that return nothing
	 */
	int update(String sql, Object... paras);

	/**
	 * @see #update(String, Object...)
	 * @param sql an SQL statement
	 */
	int update(String sql);

	/**
	 * @see #find(String, Object...)
	 */
	List<M> find(String sql, Object... paras);

	/**
	 * @see #find(String, Object...)
	 * @param sql the sql statement
	 */
	List<M> find(String sql);

	/**
	 * Find first record. I recommend add "limit 1" in your sql.
	 * @param sql an SQL statement that may contain one or more '?' IN parameter placeholders
	 * @param paras the parameters of sql
	 * @return the M object
	 */
	M findFirst(String sql, Object... paras);

	/**
	 * @see #findFirst(String, Object...)
	 * @param sql an SQL statement
	 */
	M findFirst(String sql);

	/**
	 * Find record by ids.
	 * <pre>
	 * Example:
	 * M user = Db.use().findByIds("user", "user_id", 123);
	 * M userRole = Db.use().findByIds("user_role", "user_id, role_id", 123, 456);
	 * </pre>
	 * @param idValues the id value of the record, it can be composite id values
	 */
	M findByIds(Object... idValues);

	/**
	 * Execute delete sql statement.
	 * @param sql an SQL statement that may contain one or more '?' IN parameter placeholders
	 * @param paras the parameters of sql
	 * @return the row count for <code>DELETE</code> statements, or 0 for SQL statements
	 *         that return nothing
	 */
	int delete(String sql, Object... paras);

	/**
	 * @see #delete(String, Object...)
	 * @param sql an SQL statement
	 */
	int delete(String sql);

	/**
	 * Paginate.
	 * @param pageNumber the page number
	 * @param pageSize the page size
	 * @param select the select part of the sql statement
	 * @param sqlExceptSelect the sql statement excluded select part
	 * @param paras the parameters of sql
	 * @return the Page object
	 */
	Page<M> paginate(int pageNumber, int pageSize, String select, String sqlExceptSelect, Object... paras);

	/**
	 * @see #paginate(int, int, String, String, Object...)
	 */
	Page<M> paginate(int pageNumber, int pageSize, String select, String sqlExceptSelect);

	/**
	 * Execute transaction with default transaction level.
	 * @see #tx(int, IAtom)
	 */
	boolean tx(IAtom atom);

	boolean tx(int transactionLevel, IAtom atom);

	/**
	 * 主要用于嵌套事务场景
	 *
	 * 实例：https://jfinal.com/feedback/4008
	 *
	 * 默认情况下嵌套事务会被合并成为一个事务，那么内层与外层任何地方回滚事务
	 * 所有嵌套层都将回滚事务，也就是说嵌套事务无法独立提交与回滚
	 *
	 * 使用 txInNewThread(...) 方法可以实现层之间的事务控制的独立性
	 * 由于事务处理是将 Connection 绑定到线程上的，所以 txInNewThread(...)
	 * 通过建立新线程来实现嵌套事务的独立控制
	 */
	Future<Boolean> txInNewThread(IAtom atom);

	Future<Boolean> txInNewThread(int transactionLevel, IAtom atom);

	/**
	 * Find M by cache.
	 * @see #find(String, Object...)
	 * @param cacheName the cache name
	 * @param key the key used to get date from cache
	 * @return the list of M
	 */
	List<M> findByCache(String cacheName, Object key, String sql, Object... paras);

	/**
	 * @see #findByCache(String, Object, String, Object...)
	 */
	List<M> findByCache(String cacheName, Object key, String sql);

	/**
	 * Find first record by cache. I recommend add "limit 1" in your sql.
	 * @see #findFirst(String, Object...)
	 * @param cacheName the cache name
	 * @param key the key used to get date from cache
	 * @param sql an SQL statement that may contain one or more '?' IN parameter placeholders
	 * @param paras the parameters of sql
	 * @return the M object
	 */
	M findFirstByCache(String cacheName, Object key, String sql, Object... paras);

	/**
	 * @see #findFirstByCache(String, Object, String, Object...)
	 */
	M findFirstByCache(String cacheName, Object key, String sql);

	/**
	 * Paginate by cache.
	 * @see #paginate(int, int, String, String, Object...)
	 * @return Page
	 */
	Page<M> paginateByCache(String cacheName, Object key, int pageNumber, int pageSize, String select, String sqlExceptSelect, Object... paras);

	/**
	 * @see #paginateByCache(String, Object, int, int, String, String, Object...)
	 */
	Page<M> paginateByCache(String cacheName, Object key, int pageNumber, int pageSize, String select, String sqlExceptSelect);

	/**
	 * Execute a batch of SQL INSERT, UPDATE, or DELETE queries.
	 * <pre>
	 * Example:
	 * String sql = "insert into user(name, cash) values(?, ?)";
	 * int[] result = Db.use().batch(sql, new Object[][]{{"James", 888}, {"zhanjin", 888}});
	 * </pre>
	 * @param sql The SQL to execute.
	 * @param paras An array of query replacement parameters.  Each row in this array is one set of batch replacement values.
	 * @return The number of rows updated per statement
	 */
	int[] batch(String sql, Object[][] paras, int batchSize);

	/**
	 * Execute a batch of SQL INSERT, UPDATE, or DELETE queries.
	 * <pre>
	 * Example:
	 * String sql = "insert into user(name, cash) values(?, ?)";
	 * int[] result = Db.use().batch(sql, "name, cash", modelList, 500);
	 * </pre>
	 * @param sql The SQL to execute.
	 * @param columns the columns need be processed by sql.
	 * @param modelOrRecordList model or record object list.
	 * @param batchSize batch size.
	 * @return The number of rows updated per statement
	 */
	int[] batch(String sql, String columns, List modelOrRecordList, int batchSize);


	/**
	 * Execute a batch of SQL INSERT, UPDATE, or DELETE queries.
	 * <pre>
	 * Example:
	 * int[] result = Db.use().batch(sqlList, 500);
	 * </pre>
	 * @param sqlList The SQL list to execute.
	 * @param batchSize batch size.
	 * @return The number of rows updated per statement
	 */
	int[] batch(List<String> sqlList, int batchSize);

	/**
	 * Batch save records using the "insert into ..." sql generated by the first record in recordList.
	 * Ensure all the record can use the same sql as the first record.
	 */
	int[] batchSave(List<? extends M> recordList, int batchSize);

	/**
	 * Batch update models using the attrs names of the first model in modelList.
	 * Ensure all the models can use the same sql as the first model.
	 */
	int[] batchUpdate(List<? extends M> recordList, int batchSize);

	String getSql(String key);

	// 支持传入变量用于 sql 生成。为了避免用户将参数拼接在 sql 中引起 sql 注入风险，只在 SqlKit 中开放该功能
	// String getSql(String key, Map data) {
	//     return config.getSqlKit().getSql(key, data);
	// }

	SqlPara getSqlPara(String key, M record);

	SqlPara getSqlPara(String key, Model model);

	SqlPara getSqlPara(String key, Map data);

	SqlPara getSqlPara(String key, Object... paras);

	SqlPara getSqlParaByString(String content, Map data);

	SqlPara getSqlParaByString(String content, Object... paras);

	/**
	 * 迭代处理每一个查询出来的 M 对象
	 * <pre>
	 * 例子：
	 * Db.each(record -> {
	 *    // 处理 record 的代码在此
	 *
	 *    // 返回 true 继续循环处理下一条数据，返回 false 立即终止循环
	 *    return true;
	 * }, sql, paras);
	 * </pre>
	 */
	void each(Function<M, Boolean> func, String sql, Object... paras);
}